package com.glq1218.config;


import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.glq1218.auth.service.AuthenticationService;
import com.glq1218.domain.LoginUser;
import com.glq1218.filter.JwtAuthenticationFilter;
import com.glq1218.permission.service.PermissionService;
import com.glq1218.user.entity.User;
import com.glq1218.user.mapper.UserMapper;
import com.glq1218.user.service.UserService;
import com.glq1218.util.SecurityUtils;
import com.glq1218.util.ServletUtils;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.*;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.EnableGlobalAuthentication;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.stream.Stream;

/**
 * @author glq1218
 */
@Slf4j
@Configuration
@RequiredArgsConstructor
@EnableMethodSecurity
public class SecurityConfig {
    private final UserMapper userMapper;

    private final PermissionService permissionService;

    private final JwtAuthenticationFilter jwtAuthFilter;

    /**
     * 放行Swagger
     */
    public static final String[] SWAGGER_WHITELIST = {
            "/swagger-ui/**",
            "/v3/api-docs/**",
            "/swagger-resources/**",
            "/webjars/**"
    };

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                // 允许跨域
                .cors().and()
                // 关闭表单登录
                .formLogin().disable()
                // 关闭默认登出
                .logout().disable()
                // 关闭csrf
                .csrf().disable()
                // 不通过Session获取SecurityContext
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                .authorizeHttpRequests()
                // 允许匿名访问
                .requestMatchers(requestMatchersAnonymous()).anonymous()
                // 静态资源不需要任何限制
                .requestMatchers(requestMatchersPermitAll()).permitAll()
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated().and()
                // 指定异常处理器
                .exceptionHandling()
                // 认账异常处理
                .authenticationEntryPoint(authenticationEntryPoint())
                // 授权异常处理
                .accessDeniedHandler(accessDeniedHandler()).and()
                .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
        return http.build();
    }

    /**
     * 不需要任何限制url（静态资源）
     *
     * @return 字符串数组
     */
    private String[] requestMatchersPermitAll() {
        String[] permitAll = new String[]{
                "/*.html",
                "/*.ico",
                "/*/*.html",
                "/*/*.css",
                "/ws/**",
        };
        return Stream.concat(Arrays.stream(SWAGGER_WHITELIST), Arrays.stream(permitAll)).toArray(String[]::new);
    }

    /**
     * 允许匿名访问url
     *
     * @return 字符串数组
     */
    private String[] requestMatchersAnonymous() {
        return new String[]{
                "/api/auth/**",
        };
    }

    /**
     * 密码加密规则 强散列哈希加密实现(密码加密算法)
     *
     * @return 密码加密规则
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 自定义认证失败处理器
     *
     * @return 认证失败处理
     */
    @Bean
    public AuthenticationEntryPoint authenticationEntryPoint() {
        return (request, response, authException) -> {
            authException.printStackTrace();
            if (authException instanceof BadCredentialsException) {
                ServletUtils.sendMsg(response, HttpServletResponse.SC_UNAUTHORIZED, "密码不正确");
                return;
            }
            // 当用户尝试访问安全的REST资源而不提供任何凭据时，将调用此方法发送401 响应
            ServletUtils.sendMsg(response, HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
        };
    }

    /**
     * 自定义授权异常处理
     *
     * @return 授权异常处理
     */
    @Bean
    public AccessDeniedHandler accessDeniedHandler() {
        return (request, response, accessDeniedException) -> {
            accessDeniedException.printStackTrace();
            // 当用户在没有授权的情况下访问受保护的REST资源时，将调用此方法发送403 Forbidden响应
            ServletUtils.sendMsg(response, HttpServletResponse.SC_FORBIDDEN, accessDeniedException.getMessage());
            log.warn("用户{}访问了未授权的资源", SecurityUtils.getLoginUser());
        };
    }

    /**
     * 自定义UserDetailsService
     *
     * @return UserDetailsService
     */
    @Bean
    public UserDetailsService userDetailsService() {
        return username -> {
            // 查询用户信息
            LambdaUpdateWrapper<User> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
            lambdaUpdateWrapper.eq(User::getUsername, username);
            User user = userMapper.selectOne(lambdaUpdateWrapper);
            if (user == null) {
                throw new UsernameNotFoundException("用户不存在");
            }
            if (!StringUtils.hasText(user.getPhone())) {
                throw new RuntimeException("请绑定手机号");
            }
            Set<String> permissions = permissionService.queryPermissionByUserId(user.getId());
            return LoginUser.builder()
                    .id(user.getId())
                    .username(user.getUsername())
                    .password((user.getPassword()))
                    .status(user.getStatus())
                    .nickname(user.getNickname())
                    .avatar(user.getAvatar())
                    .phone(user.getPhone())
                    .email(user.getEmail())
                    .permissions(permissions)
                    .build();
        };
    }


    @Bean
    public AuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
        authProvider.setUserDetailsService(userDetailsService());
        authProvider.setPasswordEncoder(passwordEncoder());
        authProvider.setHideUserNotFoundExceptions(false);
        return authProvider;
    }

    @Bean
    public AuthenticationManager authenticationManager() {
        return new ProviderManager(Collections.singletonList(authenticationProvider()));
    }
}
